var app = (function($) {
    var config = $("#config"),
        app = JSON.parse(config.text());

    $(document).ready(function() {
        var router = new app.router();
    });
    return app;
})(jQuery);

var TemplateView = Backbone.View.extend({
    templateName: '',
    initialize: function() {
        this.template = _.template($(this.templateName).html());
    },
    render: function() {
        var ctx = this.getContext(),
            html = this.template(ctx);
        this.$el.html(html);
    },
    getContext: function() {
        return {};
    }
});
var FormView = TemplateView.extend({
    errorTemplate: _.template('<span class="error"><%- msg %></span>'),
    events: {
        'submit form': 'submit',
    },
    submit: function(e) {
        e.preventDefault();
        this.form = $(e.currentTarget);
        this.clearError();
    },
    done: function(e) {
        if (e) {
            e.preventDefault();
        }

        this.trigger('done');
        this.remove();
    },
    modelFailure: function(model, xhr, options) {
        var errors = xhr.responseJSON;
        this.showError(errors);
    },
    failure: function(xhr, status, error) {
        var error = xhr.responseJSON;
        this.showError(error);
    },
    serializeForm: function(form) {
        return _.object(_.map(form.serializeArray(), function(item) {
            return [item.name, item.value];
        }));
    },
    showError: function(error) {
        _.map(error, function(msg, name) {
            var field = $(":input[name=" + name + ']', this.form),
                label = $("label[for=" + field.attr('id') + ']', this.form);
            if (label.length === 0) {
                label = $("label", this.form);
            }

            function appendError(msg) {
                label.before(this.errorTemplate({ msg: msg }));
            }
            _.map(msg, appendError, this);
        }, this);
    },
    clearError: function() {
        $(".error", this.form).remove();
    }
});

var LoginView = FormView.extend({
    id: 'login',
    templateName: '#login-template',
    errorTemplate: _.template('<span class="error"><%- msg %></span>'),
    events: {
        'submit form': 'submit',
    },
    submit: function(e) {
        var data = {};
        FormView.prototype.submit.apply(this, arguments);
        data = this.serializeForm(this.form);

        $.post(app.apiLogin, data)
            .success($.proxy(this.loginSuccess, this))
            .fail($.proxy(this.failure, this));
    },
    loginSuccess: function(data) {
        app.session.save(data.token);
        this.done();
    },
});
var HomepageView = TemplateView.extend({
    templateName: '#home-template',
    events: {
        'click button.add': 'renderAddForm'
    },
    renderAddForm: function(e) {
        var view = new NewSprintView(),
            link = $(e.currentTarget);
        e.preventDefault();
        link.before(view.el);
        link.hide();
        view.render();
        view.on('done', function() {
            link.show();
        });

    },
    initialize: function(opts) {
        var self = this;
        TemplateView.prototype.initialize.apply(this, arguments);
        app.collections.ready.done(function() {
            var end = new Date();
            end.setDate(end.getDate() - 7);
            end = end.toISOString().replace(/T.*/g, '');
            app.sprints.fetch({
                data: { end_min: end },
                success: $.proxy(self.render, self)
            });
        });
    },
    getContext: function() {
        return { sprints: app.sprints || null };
    }
});

var HeaderView = TemplateView.extend({
    tagName: 'header',
    templateName: '#header-template',
    events: {
        'click a.logout': 'logout'
    },
    getContext: function() {
        return { authenticated: app.session.authenticated() };
    },
    logout: function(e) {
        e.preventDefault();
        app.session.delete();
        window.location = '/';
    }
});

var NewSprintView = FormView.extend({
    templateName: '#new-sprint-template',
    className: 'new-sprint',
    events: _.extend({
        'click button.cancel': 'done',
    }, FormView.prototype.events),
    submit: function(e) {
        var self = this,
            attributes = {};
        FormView.prototype.submit.apply(this, arguments);
        attributes = this.serializeForm(this.form);
        app.collections.ready.done(function() {
            app.sprints.create(attributes, {
                wait: true,
                success: $.proxy(self.success, self),
                error: $.proxy(self.modelFailure, self)
            });
        });
    },
    success: function(model) {
        this.done();
        window.location.hash = '#sprint/' + model.get('id');
    }
});

var SprintView = TemplateView.extend({
    templateName: '#sprint-template',
    initialize: function(opts) {
        var self = this;
        TemplateView.prototype.initialize.apply(this, arguments);
        this.sprintId = opts.sprintId;
        this.sprint = null;
        this.statuses = {
            unassigned: new StatusView({
                sprint: null,
                status: 1,
                title: 'Backlog'
            }),
            todo: new StatusView({
                sprint: this.sprintId,
                status: 1,
                title: 'Not Started'
            }),
            active: new StatusView({
                sprint: this.sprintId,
                status: 2,
                title: 'In Development'
            }),
            testing: new StatusView({
                sprint: this.sprintId,
                status: 3,
                title: 'In Testing'
            }),
            done: new StatusView({
                sprint: this.sprintId,
                status: 4,
                title: 'Completed'
            })
        };
        app.collections.ready.done(function() {
            app.tasks.on('add', self.addTask, self);
            app.sprints.getOrFetch(self.sprintId).done(
                function(sprint) {
                    self.sprint = sprint;
                    self.render();
                    app.tasks.each(self.addTask, self);
                    sprint.fetchTasks();
                }).fail(function(sprint) {
                self.sprint = sprint;
                self.sprint.invalid = true;
                self.render();
            });
            app.tasks.getBacklog();
        });
    },
    getContext: function() {
        return { sprint: this.sprint };
    },
    render: function() {
        TemplateView.prototype.render.apply(this, arguments);
        _.each(this.statuses, function(view, name) {
            $(".tasks", this.$el).append(view.el);
            view.delegateEvents();
            view.render();
        }, this);

        _.each(this.tasks, function(task) {
            this.renderTask(task);
        }, this);
    },
    addTask: function(task) {
        if (task.inBacklog() || task.inSprint(this.sprint)) {
            this.tasks[task.get('id')] = task;
            this.renderTask(task);
        }
    },
    renderTask: function(task) {
        var column = task.statusClass(),
            container = this.statuses[column],
            html = _.template('<div><%- task.get("name") %></div>', { task: task });
        $(".list", container.$el).append(html);
    }
});

var AddTaskView = FormView.extend({
    templateName: '#new-task-template',
    events: _.extend({
        'click button.cancel': 'done'
    }, FormView.prototype.events),
    submit: function(e) {
        var self = this,
            attrs = {};
        FormView.prototype.submit.apply(this, arguments);
        attrs = this.serializeForm(this.form);
        app.collections.ready.done(function() {
            app.tasks.create(attrs, {
                wait: true,
                success: $.proxy(self.success, self),
                error: $.proxy(self.modelFailure, self)
            })
        })
    },
    success: function(model, resp, opts) {
        this.done();
    }
});

var StatusView = TemplateView.extend({
    tagName: 'section',
    className: 'status',
    templateName: '#status-template',
    initialize: function(opts) {
        TemplateView.prototype.initialize.apply(this, arguments);
        this.sprint = opts.sprint;
        this.status = opts.status;
        this.title = opts.title;
    },
    getContext: function() {
        return { sprint: this.sprint, title: this.title };
    }
});

app.views.HomepageView = HomepageView;
app.views.LoginView = LoginView;
app.views.HeaderView = HeaderView
app.views.SprintView = SprintView

var AppRouter = Backbone.Router.extend({
    routes: {
        '': 'home',
        'sprint/:id': 'sprint'
    },
    sprint: function(id) {
        var view = new app.views.SprintView({
            el: this.contentElement,
            sprintId: id
        });
        this.render(view);
    },
    initialize: function(opts) {
        this.contentElement = '#content';
        this.current = null;
        this.header = new app.views.HeaderView();
        $('body').prepend(this.header.el);
        this.header.render();
        Backbone.history.start();
    },
    home: function() {
        var view = new app.views.HomepageView({ el: this.contentElement });
        this.render(view);
    },
    render: function(view) {
        if (this.current) {
            this.current.undelegateEvents();
            this.current.$el = $();
            this.current.remove();
        }
        this.current = view;
        this.current.render();
    },
    route: function(route, name, callback) {
        var login;
        callback = callback || this[name];
        callback = _.wrap(callback, function(original) {
            var args = _.without(arguments, original);
            if (app.session.authenticated()) {
                original.apply(this, args);
            } else {
                $(this.contentElement).hide();
                login = new app.views.LoginView();
                $(this.contentElement).after(login.el);
                login.on('done', function() {
                    $(this.contentElement).show();
                    original.apply(this, args);
                }, this);
                login.render();
            }
        });
        return Backbone.Router.prototype.route.apply(this, [route, name, callback]);
    }
});
app.router = AppRouter;


var Session = Backbone.Model.extend({
    defaults: {
        token: null
    },
    initialize: function(opts) {
        this.opts = opts;
        $.ajaxPrefilter($.proxy(this._setupAuth, this));
        this.load();
    },
    load: function() {
        var token = localStorage.apiToken;
        if (token) {
            this.set('token', token);
        }
    },
    save: function(token) {
        this.set('token', token);
        if (token === null) {
            localStorage.removeItem('apiToken');
        } else {
            localStorage.apiToken = token;
        }
    },
    delete: function() {
        this.save(null);
    },
    authenticated: function() {
        return this.get('token') !== null;
    },
    _setupAuth: function(settings, originalOptions, xhr) {
        if (this.authenticated()) {
            xhr.setRequestHeader('Authorization', 'Token ' + this.get('token'));
        }
    }
});

function csrfSafeMethod(method) {
    return (/^(GET|HEAD|OPTIONS|TRACE)$/i.test(method));
}

function getCookie(name) {
    var cookieValue = null;
    if (document.cookie && document.cookie != '') {
        var cookies = document.cookie.split(';');
        for (var i = 0; i < cookies.length; i++) {
            var cookie = $.trim(cookies[i]);
            if (cookie.substring(0, name.length + 1) == (name + '=')) {
                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
                break;
            }
        }
    }
    return cookieValue;
}
$.ajaxPrefilter(function(settings, originalOptions, xhr) {
    var csrftoken;
    if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
        csrftoken = getCookie('csrftoken');
        xhr.setRequestHeader('X-CSRFToken', csrftoken);
    }
});
app.session = new Session();

var BaseModel = Backbone.Model.extend({
    url: function() {
        var links = this.get('links'),
            url = links && links.self;
        if (!url) {
            url = Backbone.Model.prototype.url.call(this);
        }
        return url;
    }
});

var BaseCollection = Backbone.Collection.extend({
    parse: function(res) {
        this._next = res.next;
        this._prev = res.previous;
        this._count = res.count;
        return res.results || [];
    },
    getOrFetch: function(id) {
        var result = new $.Deferred(),
            model = this.get(id);
        if (!model) {
            model = this.push({ id: id });
            model.fetch({
                success: function(model, res, opts) {
                    result.resolve(model);
                },
                error: function(model, res, opts) {
                    result.reject(model, res);
                }
            })
        } else {
            result.resolve(model);
        }
        return result;
    }
});

app.models.Sprint = BaseModel.extend({
    fetchTasks: function() {
        var links = this.get('links');
        if (links && links.tasks) {
            app.tasks.fetch({ url: links.tasks, remove: false });
        }
    }
});
app.models.Task = BaseModel.extend({
    statusClass: function() {
        var sprint = this.get('sprint'),
            status;
        if (!sprint) {
            status = 'unassigned';
        } else {
            status = ['todo', 'active', 'testing', 'done'][this.get('status') - 1];
        }
        return status;
    },
    inBacklog: function() {
        return !this.get('sprint');
    },
    inSprint: function(sprint) {
        return sprint.get('id') == this.get('sprint');
    }
});
app.models.User = BaseModel.extend({
    idAttributemodel: 'username',
});

app.collections.ready = $.getJSON(app.apiRoot);
app.collections.ready.done(function(data) {
    app.collections.Sprints = BaseCollection.extend({
        model: app.models.Sprint,
        url: data.sprint
    });

    app.sprints = new app.collections.Sprints();

    app.collections.Tasks = BaseCollection.extend({
        model: app.models.Task,
        url: data.task,
        getBacklog: function() {
            this.fetch({ remove: false, data: { backlog: 'True' } })
        }
    });
    app.tasks = new app.collections.Tasks();

    app.collections.Users = BaseCollection.extend({
        model: app.models.User,
        url: data.user
    });

    app.users = new app.collections.Users();
});